// Mutual exclusion spin locks.

#include "types.h"
#include "defs.h"
#include "param.h"
#include "x86.h"
#include "memlayout.h"
#include "mmu.h"
#include "proc.h"
#include "spinlock.h"

void
initlock(struct spinlock *lk, char *name)
{
  lk->name = name;
  lk->locked = 0;
  lk->cpu = 0;
}

// Acquire the lock.
// Loops (spins) until the lock is acquired.
// Holding a lock for a long time may cause
// other CPUs to waste time spinning to acquire it.
void
acquire(struct spinlock *lk)
{
  pushcli(); // disable interrupts to avoid deadlock.
  if(holding(lk))
    panic("acquire");

  // The xchg is atomic.
  while(xchg(&lk->locked, 1) != 0)
    ;

  // Tell the C compiler and the processor to not move loads or stores
  // past this point, to ensure that the critical section's memory
  // references happen after the lock is acquired.
  __sync_synchronize();   // 内存屏障通知编译器和处理器保证该点的前后访存顺序不要发生倒置

  // Record info about lock acquisition for debugging.
  lk->cpu = cpu;                // 该锁被编号为cpu的CPU核所持有
  getcallerpcs(&lk, lk->pcs);   // 每个锁的pcs记录了调用栈信息
}

// Release the lock.
void
release(struct spinlock *lk)
{
  if(!holding(lk))    //如果自己不持有锁则发出panic
    panic("release");

  lk->pcs[0] = 0;     // 清除该锁记录的调用栈
  lk->cpu = 0;

  // Tell the C compiler and the processor to not move loads or stores
  // past this point, to ensure that all the stores in the critical
  // section are visible to other cores before the lock is released.
  // Both the C compiler and the hardware may re-order loads and
  // stores; __sync_synchronize() tells them both to not re-order.
  __sync_synchronize();

  // Release the lock.
  lk->locked = 0;

  popcli();
}

// Record the current call stack in pcs[] by following the %ebp chain.
void
getcallerpcs(void *v, uint pcs[])   // 根据ebp反向查找调用链
{
  uint *ebp;
  int i;

  ebp = (uint*)v - 2;       // 根据第一个参数地址，找到ebp
  for(i = 0; i < 10; i++){
    if(ebp == 0 || ebp < (uint*)KERNBASE || ebp == (uint*)0xffffffff)
      break;    // 超过堆栈的最高地址，结束查找
    pcs[i] = ebp[1];     // saved %eip    // 返回地址
    ebp = (uint*)ebp[0]; // saved %ebp    // 上一级函数的栈帧起点
  }
  for(; i < 10; i++)
    pcs[i] = 0;
}

// Check whether this cpu is holding the lock.
int
holding(struct spinlock *lock)
{
  return lock->locked && lock->cpu == cpu;
}


// Pushcli/popcli are like cli/sti except that they are matched:
// it takes two popcli to undo two pushcli.  Also, if interrupts
// are off, then pushcli, popcli leaves them off.

void
pushcli(void)
{
  int eflags;

  eflags = readeflags();
  cli();
  if(cpu->ncli == 0)
    cpu->intena = eflags & FL_IF;
  cpu->ncli += 1;
}

void
popcli(void)
{
  if(readeflags()&FL_IF)
    panic("popcli - interruptible");
  if(--cpu->ncli < 0)
    panic("popcli");
  if(cpu->ncli == 0 && cpu->intena)
    sti();
}

int sh_var_for_sem_demo = 0;
int sem_used_count = 0;
struct sem sems[SEM_MAX_NUM];
// 信号量操作函数的核心代码放在spinlock而不是用独立的C文件，避免修改Makefile
// 信号量初始化
void initsem()
{
  int i;
  for ( i = 0; i < SEM_MAX_NUM; i++)
  {
    initlock(&(sems[i].lock), "semaphore");
    sems[i].allocated = 0;
  }
  return;
}

// 信号量操作的四种函数原型
// sys_sem_create的参数n_sem是初值，返回的是信号量编号，-1为出错
// 扫描sems数组，查看里面的allocated，发现未用的，将其allocated置1
// 并返回其编号。如果扫描一次后未发现则返回错误代码
int
sys_sem_create()
{
  int n_sem, i;
  if (argint(0, &n_sem) < 0)
  {
    return -1;
  }
  for (i = 0; i < SEM_MAX_NUM; i++)
  {
    acquire(&sems[i].lock);
    if (sems[i].allocated == 0)
    {
      sems[i].allocated = 1;
      sems[i].resource_count = n_sem;
      cprintf("create %d sem\n", i);
      release(&sems[i].lock);
      return i;
    }
    release(&sems[i].lock);
  }
  return -1;
}

// sys_sem_free 将指定下标id的信号量 sems[id] 对应的sems[id].lock进行加锁，并将allocated置为0（标记为未使用）
int 
sys_sem_free()
{
  int id;
  if (argint(0, &id) < 0)
  {
    return -1;
  }
  acquire(&sems[id].lock);
  if (sems[id].allocated == 1 && sems[id].resource_count > 0)
  {
    sems[id].allocated = 0;
    cprintf("free %d sem\n", id);
  }
  release(&sems[id].lock);
  return 0;
}

// sys_sem_p将指定id作为下标访问sems[id].lock获得当前信号量，对sems[id].lock加锁
// 加锁成功后对sems[id].resource_count--，接着用release()解锁退出临界区
int
sys_sem_p()
{
  int id;
  if (argint(0, &id) < 0)
  {
    return -1;
  }
  acquire(&sems[id].lock);
  sems[id].resource_count--;
  if (sems[id].resource_count < 0)      // 首次进入、或被唤醒时、资源不足
  {
    sleep(&sems[id], &sems[id].lock);   // 睡眠（会释放sems[id].lock才阻塞）
  }
  release(&sems[id].lock);              // 解锁（唤醒到此处时，重新持有sems[id].lock）
  return 0;   // 此时获得信号量资源
}

// sys_sem_v 将指定id的的信号量sems[id]加锁并将sems[id].resource_count += 1
// 如果发现sems[id].resource_count >= 0则唤醒该信号量上阻塞的进程
int
sys_sem_v()
{
  int id;
  if (argint(0, &id) < 0)
  {
    return -1;
  }
  acquire(&sems[id].lock);
  sems[id].resource_count += 1;
  if (sems[id].resource_count >= 0)  // 有阻塞等待该资源的进程
  {
    wakeup(&sems[id]);    // 唤醒等待资源的一个进程
  }
  release(&sems[id].lock);
  return 0;
}